package com.vhall.component.framework.core.loader;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.Resource;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.ServiceConfigurationError;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 自定义插件类
 *
 * @author xiaoliang.wang
 */
@Slf4j
public class CustomerExtension implements Serializable {

    public static final AtomicInteger LOAD_COUNTER = new AtomicInteger(0);

    /**
     * 配置文件路径
     */
    private String resources;

    /**
     * class type
     */
    private Class<?> type;

    /**
     * 插件列表
     */
    private List<Class<?>> classes;

    /**
     * 加载顺序
     */
    private Integer loadOrder;

    /**
     * 资源
     */
    private transient Resource resource;

    /**
     * 自定义配置初始化，包括插件列表及配置文件
     *
     * @param url         url
     * @param type        type
     * @param classLoader class loader
     */
    public CustomerExtension(URL url, Class<?> type, ClassLoader classLoader) {
        this.type = type;
        this.resources = getResourceRootPath(url);
        this.classes = parseUrl(url, classLoader);
    }

    /**
     * 解析 jar 的名称
     *
     * @param url jar url
     * @return source path
     */
    private String getResourceRootPath(URL url) {
        return url.toString().split("META-INF")[0];
    }

    /**
     * 记录加载顺序
     *
     * @param resource resource file
     */
    public Integer loadRecord(Resource resource) {
        this.resource = resource;
        this.loadOrder = LOAD_COUNTER.getAndIncrement();
        return this.loadOrder;
    }

    /**
     * 是否已被加载
     *
     * @return if loaded
     */
    public boolean loaded() {
        return Objects.nonNull(loadOrder);
    }

    /**
     * 根据提供的 profile 生成 classpath 路径
     *
     * @param profile profile
     * @return class path
     */
    public String getClassPath(String profile) {
        return this.resources.concat("*").concat(profile).concat("*");
    }

    /**
     * 解析插件配置列表
     *
     * @param url         url
     * @param classLoader class loader
     * @return class list
     * @throws ServiceConfigurationError
     */
    protected List<Class<?>> parseUrl(URL url, ClassLoader classLoader) throws ServiceConfigurationError {
        List<Class<?>> classList = new ArrayList<>();
        try (
                InputStream inputStream = url.openStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))
        ) {
            String line;
            while ((line = reader.readLine()) != null) {
                String className = parseLine(type, url, line);
                if (StringUtils.isNotBlank(className)) {
                    Class<?> clz = loadClass(classLoader, className);
                    if (!classList.contains(clz)) {
                        classList.add(clz);
                    }
                }
            }
        } catch (Exception e) {
            log.error("", e);
        }
        return classList;
    }

    /**
     * 重新构建插件列表
     *
     * @param excludes 排除项目
     */
    public void replaceClasses(List<String> excludes) {
        List<Class<?>> excludeCasses = new ArrayList<>(excludes.size());
        ClassLoader classLoader = this.getClass().getClassLoader();
        excludes.forEach(exclude -> excludeCasses.add(loadClass(classLoader, exclude)));
        for (Class<?> exclude : excludeCasses) {
            this.classes.removeIf(plugin -> plugin == exclude);
        }
    }

    private String parseLine(Class<?> type, URL url, String line) throws ServiceConfigurationError {
        int ci = line.indexOf('#');
        if (ci >= 0) {
            line = line.substring(0, ci);
        }

        line = line.trim();
        if (line.length() <= 0) {
            return null;
        }
        if ((line.indexOf(' ') >= 0) || (line.indexOf('\t') >= 0)) {
            throw new RuntimeException(type.getName() + ": " + url + ":" + line
                    + ": Illegal spi configuration-file syntax: " + line);
        }

        int cp = line.codePointAt(0);
        if (!Character.isJavaIdentifierStart(cp)) {
            throw new RuntimeException(type.getName() + ": " + url + ":" + line
                    + ": Illegal spi provider-class name: " + line);
        }

        for (int i = Character.charCount(cp); i < line.length(); i += Character.charCount(cp)) {
            cp = line.codePointAt(i);
            if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) {
                throw new RuntimeException(type.getName() + ": " + url + ":" + line
                        + ": Illegal spi provider-class name: " + line);
            }
        }

        return line;
    }

    private Class<?> loadClass(ClassLoader classLoader, String className) {
        Class<?> clz = null;
        try {
            if (classLoader == null) {
                clz = Class.forName(className);
            } else {
                clz = Class.forName(className, true, classLoader);
            }
            // 检查 class
            // this.checkExtensionType(clz);
        } catch (Exception e) {
            log.error(MessageFormat.format("加载异常：【{0}】【{1}】", e, className));
        }
        return clz;
    }

    public List<Class<?>> getClasses() {
        return classes;
    }

    public Class<?> getType() {
        return type;
    }

    public String getResources() {
        return resources;
    }

    public Integer getLoadOrder() {
        return loadOrder;
    }

    public Resource getResource() {
        return resource;
    }


    @Override
    public String toString() {
        return "[classes=" + classes + ", resource=" + resource + "]";
    }

}
